using System;
using System.Xml;
using System.Collections;
using System.Drawing;
using System.Reflection;
using System.Windows.Forms;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Text;
using System.Text.RegularExpressions;

namespace FormDesigner
{
	/// <summary>
	/// This class is able to generate a GUI definition out of a XML file.
	/// </summary>
	public class XmlFormReader
	{
		IDesignerHost host;

		public event EventHandler OnRootObjectCreate;
		
		ArrayList Components = new ArrayList();
		/// <summary>
		/// Creates a new instance of GuiXmlGenerator.
		/// </summary>
		/// <param name="customizationObject">
		/// The object to customize. (should be a control or form)
		/// This is object, because this class may be extended later.
		/// </param>
		/// <param name="fileName">
		/// The filename of the XML definition file to load.
		/// </param>
		public XmlFormReader(IDesignerHost host)
		{
			this.host = host;
		}

		// <summary>
		/// Loads the XML definition from fileName and sets up the control.
		/// </summary>
		public void LoadXml(string xmlContent)
		{
			XmlDocument doc = new XmlDocument();
			doc.LoadXml(xmlContent);
			foreach(XmlElement el in doc.DocumentElement.ChildNodes)
			{
				CreateObject(el, true);
			}
			foreach(XmlElement el in doc.DocumentElement.ChildNodes)
			{
				ReCreateObject(el, true);
			}
		}

		public object CreateObject(string xmlContent)
		{
			XmlDocument doc = new XmlDocument();
			doc.LoadXml(xmlContent);
			
			return CreateObject(doc.DocumentElement, true);
		}

		void SetUpObject(object currentObject, XmlElement element)
		{
			foreach (XmlNode subNode in element.ChildNodes) 
			{
				if (subNode is XmlElement) 
				{
					XmlElement subElement = (XmlElement)subNode;
					SetAttributes(currentObject, subElement);
				}
			}
		}

		void SetValue(object o, string propertyName, string val)
		{
			try 
			{
				PropertyInfo propertyInfo = o.GetType().GetProperty(propertyName);
				if (val.StartsWith("{") && val.EndsWith("}")) 
				{
					val = val.Substring(1, val.Length - 2);
					object propertyObject = null;
					if (propertyInfo.CanWrite) 
					{
						Type type = host.GetType(propertyInfo.PropertyType.FullName);
						propertyObject = type.Assembly.CreateInstance(propertyInfo.PropertyType.FullName);
					} 
					else 
					{
						propertyObject = propertyInfo.GetValue(o, null);
					}
					
					Regex propertySet  = new Regex(@"(?<Property>[\w]+)\s*=\s*(?<Value>[\w\d]+)", RegexOptions.Compiled);
					Match match = propertySet.Match(val);
					while (true) 
					{
						if (!match.Success) 
						{
							break;
						}
						SetValue(propertyObject, match.Result("${Property}"), match.Result("${Value}"));
						match = match.NextMatch();
					}
					
					if (propertyInfo.CanWrite) 
					{
						propertyInfo.SetValue(o, propertyObject, null);
					}
				} 
				else if (propertyInfo.PropertyType.IsEnum) 
				{
					propertyInfo.SetValue(o, Enum.Parse(propertyInfo.PropertyType, val), null);
				} 
				else if (propertyInfo.PropertyType == typeof(Color)) 
				{
					string color = val.Substring(val.IndexOf('[') + 1).Replace("]", "");
					string[] argb = color.Split(',', '=');
					if (argb.Length > 1) 
					{
						propertyInfo.SetValue(o, Color.FromArgb(Int32.Parse(argb[1]), Int32.Parse(argb[3]), Int32.Parse(argb[5]), Int32.Parse(argb[7])), null);
					} 
					else 
					{
						propertyInfo.SetValue(o, Color.FromName(color), null);
					}
				} 
				else if (propertyInfo.PropertyType == typeof(Font)) 
				{
					string[] font = val.Split(',', '=');
					propertyInfo.SetValue(o, new Font(font[1], Int32.Parse(font[3])), null);
				} 
				else if (propertyInfo.PropertyType == typeof(System.Windows.Forms.Cursor)) 
				{
					string[] cursor = val.Split('[', ']', ' ', ':');
					PropertyInfo cursorProperty = typeof(System.Windows.Forms.Cursors).GetProperty(cursor[3]);
					if (cursorProperty != null) 
					{
						propertyInfo.SetValue(o, cursorProperty.GetValue(null, null), null);
					}
				} 
				else if (propertyInfo.PropertyType.IsClass && val != "") 
				{
					object ob = this.FinComponent(val);
					if(ob != null)
					{
						propertyInfo.SetValue(o, ob, null);
					}
					else
					{
						propertyInfo.SetValue(o, Convert.ChangeType(val, propertyInfo.PropertyType), null);
					}
				} 
				else 
				{
					propertyInfo.SetValue(o, Convert.ChangeType(val, propertyInfo.PropertyType), null);
				}
			} 
			catch (Exception e) 
			{
				Console.WriteLine(e.ToString());
				throw new ApplicationException("error while setting property " + propertyName + " of object "+ o.ToString() + " to value '" + val+ "'");
			}
		}

		void SetAttributes(object o, XmlElement el)
		{
			if (el.Attributes["value"] != null) 
			{
				string val = el.Attributes["value"].InnerText;
				try 
				{
					SetValue(o, el.Name, val);
				} 
				catch (Exception) {}
			} 
			else 
			{
				PropertyInfo propertyInfo = o.GetType().GetProperty(el.Name);
				object pv = propertyInfo.GetValue(o, null);
				if (pv is IList) 
				{
					foreach (XmlNode subNode in el.ChildNodes) 
					{
						if (subNode is XmlElement)
						{
							XmlElement subElement = (XmlElement)subNode;
							object collectionObject = this.CreateObject(subElement, false);
							
							if (collectionObject != null) 
							{
								try 
								{
									((IList)pv).Add(collectionObject);
								} 
								catch (Exception e) 
								{
									Console.WriteLine("Exception while adding to a collection:" + e.ToString());
								}
							}
						}
					}
				}
			}
		}

		private object CreateObject(XmlElement objectElement, bool suspend)
		{
			Type type = host.GetType(objectElement.Name);
			
			if (objectElement.Attributes["value"] != null) 
			{
				try 
				{
					return System.Convert.ChangeType(objectElement.Attributes["value"].InnerText, type);
				} 
				catch (Exception) {}
			}
			
			if (type == null) 
			{
				return null;
			}

			string componentName = null;
			if (objectElement["Name"] != null &&
				objectElement["Name"].Attributes["value"] != null) 
			{
				componentName = objectElement["Name"].Attributes["value"].InnerText;
			}

			IComponent ic =	host.CreateComponent(type, componentName);
			this.Components.Add(ic);
			if(this.Components.Count == 1)
			{
				if(OnRootObjectCreate != null)
				{
					OnRootObjectCreate(ic, new EventArgs());
				}
			}

			if(suspend && ic is Control)
			{
				((Control)ic).SuspendLayout();
			}

			SetUpObject((object)ic, objectElement);

			if(suspend && ic is Control)
			{
				((Control)ic).ResumeLayout(false);
			}

			return (object)ic;
		}

		private void ReSetAttributes(object o, XmlElement el)
		{
			if (el.Attributes["value"] != null) 
			{
				string val = el.Attributes["value"].InnerText;
				try 
				{
					SetValue(o, el.Name, val);
				} 
				catch (Exception) {}
			} 
			else 
			{
				PropertyInfo propertyInfo = o.GetType().GetProperty(el.Name);
				object pv = propertyInfo.GetValue(o, null);
				if (pv is IList) 
				{
					foreach (XmlNode subNode in el.ChildNodes) 
					{
						if (subNode is XmlElement)
						{
							XmlElement subElement = (XmlElement)subNode;
							object collectionObject = this.ReCreateObject(subElement, false);
							
							if (collectionObject != null) 
							{
								try 
								{
									((IList)pv).Add(collectionObject);
								} 
								catch (Exception e) 
								{
									Console.WriteLine("Exception while adding to a collection:" + e.ToString());
								}
							}
						}
					}
				}
			}
		}

		void ReSetUpObject(object currentObject, XmlElement element)
		{
			foreach (XmlNode subNode in element.ChildNodes) 
			{
				if (subNode is XmlElement) 
				{
					XmlElement subElement = (XmlElement)subNode;
					ReSetAttributes(currentObject, subElement);
				}
			}
		}

		private object ReCreateObject(XmlElement objectElement, bool suspend)
		{
			string componentName = null;
			if (objectElement["Name"] != null &&
				objectElement["Name"].Attributes["value"] != null) 
			{
				componentName = objectElement["Name"].Attributes["value"].InnerText;
			}

			object ic = null;
			
			ic = FinComponent(componentName +" ["+objectElement.Name+"]");
			
			if(ic != null)
			{
				if(suspend && ic is Control)
				{
					((Control)ic).SuspendLayout();
				}

				ReSetUpObject(ic, objectElement);

				if(suspend && ic is Control)
				{
					((Control)ic).ResumeLayout(false);
				}
			}

			return ic;

		}

		object FinComponent(string name)
		{
			object ic;
			
			for(int i=0; i<this.host.Container.Components.Count; i++)
			{
				ic = (object)this.host.Container.Components[i];
				
				string s = ic.ToString();

				if(ic.ToString().Length >= name.Length && ic.ToString().Substring(0, name.Length) == name)
				{
					return ic;
				}
			}
			return null;
		}
	}
}
